/* -*- Mode: C; c-basic-offset:4 ; -*- */
/*
 * Copyright (c) 2014-2019 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2014      Research Organization for Information Science
 *                         and Technology (RIST). All rights reserved.
 * Copyright (c) 2015 Cisco Systems, Inc.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "ompi_config.h"
#include "ompi/datatype/ompi_datatype.h"
#include "opal/datatype/opal_convertor.h"
#include "opal/datatype/opal_datatype_internal.h"
#include "opal/runtime/opal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define N 331

uint32_t remote_arch = 0xffffffff;
bool report_all_errors = true;

struct foo_t {
    int i[3];
    double d[3];
} foo = {0}, *bar = NULL;

struct pfoo_t {
    int i[2];
    double d[2];
} pfoo = {0}, *pbar = NULL;

static void print_hex(void *ptr, int count, char *epilog, char *prolog)
{
    if (NULL != epilog)
        fprintf(stderr, "%s", epilog);
    for (int i = 0; i < count; i++) {
        fprintf(stderr, "%02x", (unsigned int) (((unsigned char *) ptr)[i]));
    }
    if (NULL != prolog)
        fprintf(stderr, "%s", prolog);
}

static void print_bar_pbar(struct foo_t *_bar, struct pfoo_t *_pbar)
{
    print_hex(&_bar->i[0], sizeof(int), NULL, " ");
    print_hex(&_bar->i[1], sizeof(int), "[", "] ");
    print_hex(&_bar->i[2], sizeof(int), NULL, " ");
    print_hex(&_bar->d[0], sizeof(double), NULL, " ");
    print_hex(&_bar->d[1], sizeof(double), "[", "] ");
    print_hex(&_bar->d[2], sizeof(double), NULL, "\n");

    print_hex(&_pbar->i[0], sizeof(int), NULL, "            ");
    print_hex(&_pbar->i[1], sizeof(int), NULL, " ");
    print_hex(&_pbar->d[0], sizeof(double), NULL, "                    ");
    print_hex(&_pbar->d[1], sizeof(double), NULL, "\n");
}

static void print_stack(opal_convertor_t *conv)
{
    printf("Stack pos %d [converted %" PRIsize_t "/%" PRIsize_t "]\n", conv->stack_pos,
           conv->bConverted, conv->local_size);
    for (uint32_t i = 0; i <= conv->stack_pos; i++) {
        printf("[%u] index %d, type %s count %" PRIsize_t " disp %p\n", i, conv->pStack[i].index,
               opal_datatype_basicDatatypes[conv->pStack[i].type]->name, conv->pStack[i].count,
               (void *) conv->pStack[i].disp);
    }
    printf("\n");
}

static int testcase(ompi_datatype_t *newtype, size_t arr[][2])
{
    int i, j, errors = 0;
    struct iovec a;
    unsigned int iov_count;
    size_t max_data;
    size_t pos;
    opal_convertor_t *pConv;

    for (j = 0; j < N; ++j) {
        pbar[j].i[0] = 123 + j;
        pbar[j].i[1] = 789 + j;
        pbar[j].d[0] = 123.456 + j;
        pbar[j].d[1] = 789.123 + j;
        memset(&bar[j].i[0], 0xFF, sizeof(int));
        memset(&bar[j].i[2], 0xFF, sizeof(int));
        bar[j].i[1] = 0;
        memset(&bar[j].d[0], 0xFF, sizeof(double));
        memset(&bar[j].d[2], 0xFF, sizeof(double));
        bar[j].d[1] = 0.0;
    }

    pConv = opal_convertor_create(remote_arch, 0);
    if (OPAL_SUCCESS != opal_convertor_prepare_for_recv(pConv, &(newtype->super), N, bar)) {
        printf("Cannot attach the datatype to a convertor\n");
        return OMPI_ERROR;
    }

    for (i = 0; 0 != arr[i][0]; i++) {
        /* add some garbage before and after the source data */
        a.iov_base = malloc(arr[i][0] + 2048);
        if (NULL == a.iov_base) {
            printf("cannot malloc iov_base\n");
            return 1;
        }
        memset(a.iov_base, 0xAA, 1024);
        memcpy((char *) a.iov_base + 1024, (char *) pbar + arr[i][1], arr[i][0]);
        memset((char *) a.iov_base + 1024 + arr[i][0], 0xAA, 1024);
        a.iov_base = (char *) a.iov_base + 1024;
        a.iov_len = arr[i][0];
        iov_count = 1;
        max_data = a.iov_len;
        pos = arr[i][1];
        opal_convertor_set_position(pConv, &pos);
        print_stack(pConv);
        assert(arr[i][1] == pos);
        opal_convertor_unpack(pConv, &a, &iov_count, &max_data);
        a.iov_base = (char *) a.iov_base - 1024;
        free(a.iov_base);
    }

    for (j = 0; j < N; ++j) {
        if (bar[j].i[0] != pbar[j].i[0] || bar[j].i[1] != 0 || bar[j].i[2] != pbar[j].i[1]
            || bar[j].d[0] != pbar[j].d[0] || bar[j].d[1] != 0.0 || bar[j].d[2] != pbar[j].d[1]) {
            if (0 == errors || report_all_errors) {
                ptrdiff_t displ;
                char *error_location = "in gaps";
                if (bar[j].i[0] != pbar[j].i[0]) {
                    displ = (char *) &bar[j].i[0] - (char *) &bar[0];
                    error_location = "i[0]";
                } else if (bar[j].i[2] != pbar[j].i[1]) {
                    displ = (char *) &bar[j].i[1] - (char *) &bar[0];
                    error_location = "i[2]";
                } else if (bar[j].d[0] != pbar[j].d[0]) {
                    displ = (char *) &bar[j].d[0] - (char *) &bar[0];
                    error_location = "d[0]";
                } else if (bar[j].d[2] != pbar[j].d[1]) {
                    displ = (char *) &bar[j].d[1] - (char *) &bar[0];
                    error_location = "d[2]";
                } else {
                    displ = (char *) &bar[j] - (char *) &bar[0];
                }
                for (i = 0; 0 != arr[i][0]; i++) {
                    if ((displ >= arr[i][1]) && (displ <= (arr[i][1] + arr[i][0]))) {
                        fprintf(stderr,
                                "Problem encountered %li bytes into the %d unpack [%" PRIsize_t
                                ":%" PRIsize_t "]\n",
                                displ - arr[i][1], i, arr[i][1], arr[i][0]);
                        break;
                    }
                }

                (void) opal_datatype_dump(&newtype->super);
                fprintf(stderr,
                        "ERROR ! struct %d/%d in field %s, ptr = %p"
                        " got (%d,%d,%d,%g,%g,%g) expected (%d,%d,%d,%g,%g,%g)\n",
                        j, N, error_location, (void *) &bar[j], bar[j].i[0], bar[j].i[1],
                        bar[j].i[2], bar[j].d[0], bar[j].d[1], bar[j].d[2], pbar[j].i[0], 0,
                        pbar[j].i[1], pbar[j].d[0], 0.0, pbar[j].d[1]);
                print_bar_pbar(&bar[j], &pbar[j]);
                if (report_all_errors)
                    fprintf(stderr, "\n\n");
            }
            errors++;
        }
    }
    OBJ_RELEASE(pConv);
    return errors;
}

static int unpack_ooo(void)
{
    ompi_datatype_t *t1;
    ompi_datatype_t *t2;
    ompi_datatype_t *type[2];
    ompi_datatype_t *newtype;
    MPI_Aint disp[2];
    int len[2], rc;

    rc = ompi_datatype_create_vector(2, 1, 2, MPI_INT, &t1);
    if (OMPI_SUCCESS != rc) {
        fprintf(stderr, "could not create vector t1\n");
        return 1;
    }
    rc = ompi_datatype_commit(&t1);
    if (OMPI_SUCCESS != rc) {
        fprintf(stderr, "could not commit vector t1\n");
        return 1;
    }

    rc = ompi_datatype_create_vector(2, 1, 2, MPI_DOUBLE, &t2);
    if (OMPI_SUCCESS != rc) {
        fprintf(stderr, "could not create vector t2\n");
        return 1;
    }
    rc = ompi_datatype_commit(&t2);
    if (OMPI_SUCCESS != rc) {
        fprintf(stderr, "could not commit vector t2\n");
        return 1;
    }

    /*
     * btl=0x7f7823672580 bytes_received=992 data_offset=0
     * btl=0x7f7823260420 bytes_received=1325 data_offset=992
     * btl=0x7f7823672580 bytes_received=992 data_offset=2317
     * btl=0x7f7823672580 bytes_received=992 data_offset=3309
     * btl=0x7f7823672580 bytes_received=992 data_offset=4301
     * btl=0x7f7823672580 bytes_received=992 data_offset=5293
     * btl=0x7f7823672580 bytes_received=992 data_offset=6285
     * btl=0x7f7823672580 bytes_received=667 data_offset=7277
     */
    size_t test1[9][2] = {
        {992, 0},
        {1325, 0 + 992},
        {992, 992 + 1325 /* = 2317 */},
        {992, 2317 + 992 /* = 3309 */},
        {992, 3309 + 992 /* = 4301 */},
        {992, 4301 + 992 /* = 5293 */},
        {992, 5293 + 992 /* = 6285 */},
        {667, 6285 + 992 /* = 7277 */},
        {0, -1},
    };

    /*
     * btl=0x7f80bc545580 bytes_received=992 data_offset=0
     * btl=0x7f80bc545580 bytes_received=992 data_offset=2317
     * btl=0x7f80bc545580 bytes_received=992 data_offset=3309
     * btl=0x7f80bc545580 bytes_received=992 data_offset=4301
     * btl=0x7f80bc545580 bytes_received=992 data_offset=5293
     * btl=0x7f80bc545580 bytes_received=992 data_offset=6285
     * btl=0x7f80bc133420 bytes_received=1325 data_offset=992
     * btl=0x7f80bc545580 bytes_received=667 data_offset=7277
     */
    size_t test2[9][2] = {
        {992, 0},    {992, 2317}, {992, 3309}, {992, 4301}, {992, 5293},
        {992, 6285}, {1325, 992}, {667, 7277}, {0, -1},
    };

    /* trimmed version of test2 */
    size_t test3[9][2] = {
        {992, 0}, {4960, 2317}, {1325, 992}, {667, 7277}, {0, -1},
    };

    /* an other test case */
    size_t test4[9][2] = {
        {992, 0}, {992, 2976}, {992, 1984}, {992, 992}, {3976, 3968}, {0, -1},
    };

    disp[0] = (long) (&foo.i[0]) - (long) &foo;
    disp[1] = (long) (&foo.d[0]) - (long) &foo;

    type[0] = t1;
    type[1] = t2;

    len[0] = 1;
    len[1] = 1;

    rc = ompi_datatype_create_struct(2, len, disp, type, &newtype);
    if (OMPI_SUCCESS != rc) {
        fprintf(stderr, "could not create struct\n");
        return 1;
    }
    rc = ompi_datatype_commit(&newtype);
    if (OMPI_SUCCESS != rc) {
        fprintf(stderr, "could not create struct\n");
        return 1;
    }

    pbar = (struct pfoo_t *) malloc(N * sizeof(struct pfoo_t));
    if (NULL == pbar) {
        fprintf(stderr, "could not malloc pbar\n");
        return 1;
    }
    bar = (struct foo_t *) malloc(N * sizeof(struct foo_t));
    if (NULL == bar) {
        fprintf(stderr, "could not malloc bar\n");
        return 1;
    }

    if (0 != testcase(newtype, test1)) {
        printf("test1 failed\n");
        return 2;
    }

    if (0 != testcase(newtype, test2)) {
        printf("test2 failed\n");
        return 2;
    }

    if (0 != testcase(newtype, test3)) {
        printf("test3 failed\n");
        return 2;
    }

    if (0 != testcase(newtype, test4)) {
        printf("test4 failed\n");
        return 2;
    }

    /* test the automatic destruction pf the data */
    ompi_datatype_destroy(&newtype);
    assert(newtype == NULL);

    return rc;
}

int main(int argc, char *argv[])
{
    int rc;

    opal_init(&argc, &argv);
    ompi_datatype_init();
    /**
     * By default simulate homogeneous architectures.
     */
    remote_arch = opal_local_arch;

    printf("\n\n#\n * TEST UNPACK OUT OF ORDER\n #\n\n");
    rc = unpack_ooo();
    if (rc == 0) {
        printf("unpack out of order [PASSED]\n");
        return 0;
    }
    printf("unpack out of order [NOT PASSED]\n");
    return -1;
}
